package controllers

import (
	"encoding/json"
	"fmt"
	"gateway-api/constants"
	"gateway-api/tools"
	"io/ioutil"
	"strconv"

	pbDemo "gateway-api/proto/demo"

	"github.com/dtm-labs/client/dtmcli"
	"github.com/dtm-labs/client/workflow"
	"github.com/gin-gonic/gin"
	"github.com/golang/protobuf/proto"
	"github.com/lithammer/shortuuid/v3"
)

/*
 +----------------------------------------------------------------------
 + Title        : WorkflowSagaController
 + Author       : huwl
 + Version      : 2.0.0
 + Initial-Time : 2020-11-23
 + Last-time    : 这个文件最后修改的时间 + 修改人的名称
 + Desc         : DTM workflow saga 分布式事务控制层：工作量少，并且能够解决绝大部分业务的需求。
 +				: 其核心思想是将长事务拆分为多个短事务，由Saga事务协调器协调，
 +				: 如果每个短事务都成功提交完成,那么全局事务就正常完成,如果某个步骤失败,则根据相反顺序一次调用补偿操作。
 +
 + SUCCESS      : 表示成功
 + FAILURE      : 表示失败，这个失败是指确定的失败结果，不需要重试。
                  例如子事务因转账余额不足而失败，dtm服务因为事务已完成不允许再次提交等
 + ONGOING      : 表示未完成，还在正常进行中，此时dtm服务器需要采用固定间隔重试，而不是指数退避算法重试
				  对tcc不管用,在tcc模式下和FAILURE状态一样

 + other status : 表示临时错误，采用指数退避算法重试，避免出现故障或者bug，导致大量重试，导致负载过高
 + SUCCESS http : 状态码 200； grpc： err == nil
 + FAILURE http : 状态码 409 (http.StatusConflict)；grpc Code == Aborted tcc 失败、未完成的状态必须使用409、425
 + ONGOING http : 状态码 425 (http.StatusTooEarly)；grpc Code == FailedPrecondition tcc 失败、未完成的状态必须使用409、425
 +----------------------------------------------------------------------
*/
type WorkflowSagaController struct {
	BaseController
}

/**
 * WorkflowSaga grpc 方式
 *
 * @return Json
 * @author huwl
 */
func (control WorkflowSagaController) WSagaGrpc(ctx *gin.Context) {
	amount, _ := strconv.Atoi(ctx.DefaultQuery("amount", "30"))
	transInResult := ctx.DefaultQuery("trans_in_result", "")
	transOutResult := ctx.DefaultQuery("trans_out_result", "")

	wfName := tools.GenerateRandomStr(7)
	err := workflow.Register(wfName, func(wf *workflow.Workflow, data []byte) error {
		var reqGrpc pbDemo.ReqGrpc
		err := proto.Unmarshal(data, &reqGrpc)
		if err != nil {
			return err
		}
		// fmt.Println("------------reqGrpc =", reqGrpc)

		dsc := pbDemo.NewDemoSrvClient(tools.UserServiceClientConn)

		// 转出子事务
		wf.NewBranch().OnRollback(func(bb *dtmcli.BranchBarrier) error {
			_, err := dsc.TransOutRevert(wf.Context, &reqGrpc)
			return err
		})
		_, err = dsc.TransOut(wf.Context, &reqGrpc)
		if err != nil {
			return err
		}

		// 转入子事务
		wf.NewBranch().OnRollback(func(bb *dtmcli.BranchBarrier) error {
			_, err := dsc.TransInRevert(wf.Context, &reqGrpc)
			return err
		})
		_, err = dsc.TransIn(wf.Context, &reqGrpc)
		return err
	})
	if err != nil {
		control.BaseError(ctx, control.WithMessage(err.Error()))
		return
	}

	gid := shortuuid.New()
	b, _ := proto.Marshal(&pbDemo.ReqGrpc{
		Amount:         int64(amount),
		TransOutResult: transOutResult,
		TransInResult:  transInResult,
	})
	err = workflow.Execute(wfName, gid, b)
	if err != nil {
		control.BaseError(ctx, control.WithMessage(err.Error()))
		return
	}

	control.BaseSuccess(ctx, control.WithData(gin.H{
		"gid": gid,
	}))
}

/**
 * WorkflowSaga http 方式
 *
 * @return Json
 * @author huwl
 */
func (control WorkflowSagaController) WSagaHttp(ctx *gin.Context) {
	amount, _ := strconv.Atoi(ctx.DefaultQuery("amount", "30"))
	transInResult := ctx.DefaultQuery("trans_in_result", "")
	transOutResult := ctx.DefaultQuery("trans_out_result", "")

	req := &ReqHTTP{
		Amount:         amount,
		TransOutResult: transOutResult,
		TransInResult:  transInResult,
	}

	// workflow初始化HTTP
	workflow.InitHTTP(tools.DtmHttpServer, tools.HttpServiceUrl+"/workflowSaga/resume")

	// 创建gid
	gid := shortuuid.New()

	// 定义workflow name
	wfName := tools.GenerateRandomStr(5)

	// 注册workflow
	err := workflow.Register(wfName, func(wf *workflow.Workflow, data []byte) error {
		var req *ReqHTTP
		err := json.Unmarshal(data, &req)
		if err != nil {
			return err
		}

		// 转出子事务
		_, err = wf.NewBranch().OnRollback(func(bb *dtmcli.BranchBarrier) error {
			_, err := wf.NewRequest().SetBody(req).Post(tools.HttpServiceUrl + "/workflowSaga/transOutRevert")
			return err
		}).NewRequest().SetBody(req).Post(tools.HttpServiceUrl + "/workflowSaga/transOut")
		if err != nil {
			return err
		}

		// 转入子事务
		_, err = wf.NewBranch().OnRollback(func(bb *dtmcli.BranchBarrier) error {
			_, err := wf.NewRequest().SetBody(req).Post(tools.HttpServiceUrl + "/workflowSaga/transInRevert")
			return err
		}).NewRequest().SetBody(req).Post(tools.HttpServiceUrl + "/workflowSaga/transIn")
		if err != nil {
			return err
		}

		return nil
	})
	if err != nil {
		control.BaseError(ctx, control.WithMessage(err.Error()))
		return
	}

	b, _ := json.Marshal(req)

	// workflow执行
	err = workflow.Execute(wfName, gid, b)
	if err != nil {
		control.BaseError(ctx, control.WithMessage(err.Error()))
		return
	}

	control.BaseSuccess(ctx, control.WithData(gin.H{
		"gid": gid,
	}))
}

/**
 * WorkflowSaga 恢复
 *
 * @return Json
 * @author huwl
 */
func (control WorkflowSagaController) Resume(ctx *gin.Context) {
	data, err := ioutil.ReadAll(ctx.Request.Body)
	if err != nil {
		control.BaseError(ctx, control.WithMessage(err.Error()))
		return
	}

	err = workflow.ExecuteByQS(ctx.Request.URL.Query(), data)
	if err != nil {
		control.BaseError(ctx, control.WithMessage(err.Error()))
		return
	}

	control.BaseSuccess(ctx, control.WithMessage("workflow resume"))
}

/**
 * 转出
 *
 * @return Json
 * @author huwl
 */
func (control WorkflowSagaController) TransOut(ctx *gin.Context) {
	result := ctx.DefaultPostForm("trans_out_result", "")
	fmt.Println("result =", result)

	// 在项目中必须在执行核心代码失败时才能使用409,参数校验失败之类的错误不能使用409
	// 因为一旦409就要执行取消、补偿操作将导致数据错乱
	// 例如: 扣除库存时执行扣除库存失败时返回409
	// 还原库存的时候最好判断一下是否真的已扣除,如何判断是否已扣除可以在
	if result == "FAILURE" {
		// 返回409终止状态
		control.BaseError(ctx, control.WithStatusCode(constants.ResultFailure), control.WithData("TransOut code=409 FAILURE"))
		return
	} else if result == "ONGOING" {
		// 返回表示未完成
		control.BaseError(ctx, control.WithStatusCode(constants.ResultOngoing), control.WithData("TransOut code=425 ONGOING"))
		return
	}
	control.BaseSuccess(ctx, control.WithData("TransOut"))
}

/**
 * 补偿转出
 *
 * @return Json
 * @author huwl
 */
func (control WorkflowSagaController) TransOutRevert(ctx *gin.Context) {
	control.BaseSuccess(ctx, control.WithData("TransOutRevert"))
}

/**
 * 转入
 *
 * @return Json
 * @author huwl
 */
func (control WorkflowSagaController) TransIn(ctx *gin.Context) {
	result := ctx.DefaultPostForm("trans_in_result", "")
	fmt.Println("result =", result)

	// 在项目中必须在执行核心代码失败时才能使用409,参数校验失败之类的错误不能使用409
	// 因为一旦409就要执行取消、补偿操作将导致数据错乱
	// 例如: 扣除库存时执行扣除库存失败时返回409
	// 还原库存的时候最好判断一下是否真的已扣除,如何判断是否已扣除可以在
	if result == "FAILURE" {
		// 返回409终止状态
		control.BaseError(ctx, control.WithStatusCode(constants.ResultFailure), control.WithData("TransIn code=409 FAILURE"))
		return
	} else if result == "ONGOING" {
		// 返回表示未完成
		control.BaseError(ctx, control.WithStatusCode(constants.ResultOngoing), control.WithData("TransIn code=425 ONGOING"))
		return
	}
	control.BaseSuccess(ctx, control.WithData("TransIn SUCCESS"))
}

/**
 * 补偿转入
 *
 * @return Json
 * @author huwl
 */
func (control WorkflowSagaController) TransInRevert(ctx *gin.Context) {
	control.BaseSuccess(ctx, control.WithData("TransInRevert"))
}
